Chapter 14: Inheritance and concept of namespace
From book Python Programming (Problem solving, Packages and Libraries) published by McGraw Hill Education (India) Private limited. By:

  • Anurag Gupta
  • G. P. Biswas

Note the following:-

  1. This html document is meant as an accompaniment to Chapter 14 Inheritance and concept of namespace .
  2. The document contains scripts executed on IDLE as well as on Jupyter notebook.
  3. The scripts executed on Jupyter can be directly copied and run into a Jupyter notebook or some other IDE (Like Pycharm or Eclipse with PyDev or Visual studio).
  4. However the scripts on IDLE also contain the >>> symbol and therefore cannot be directly executed. If you want to execute them on IDLE or Jupyter, you need to manually remove the >>> symbol.
  5. Wherever needed some background material from the book is also included to help you better understand the scripts
  6. The topic numbers given on each paragraph match the topic numbers of the book, so you can easily identify the topics and corresponding scripts.
  7. In some of the scripts, the file paths give are that of the author's computer. You need to replace them with file paths of your own computer.
  8. At some places, to improve readability, page numbers of the book are indicated in green font like:- See Page 181 of the book
  9. This document was first created as a Jupyter Notebook as combination of Markdown and code cells (extension .ipynb) and then downloaded as html. If someone wants to "modify" or "extend' this document, you may ask for the original .ipynb file by sending me an e-mail at:- 999.anuraggupta@gmail.com

14.2. Basics of inheritance in Python
14.2.1. Introduction to subclassing (Inheritance)
Suppose you have a class named BaseClass, then you can derive another class say DerivedClass from it.

class DerivedClass(BaseClass):#

This is shown in the following example:

In [1]:
class BaseClass:
    pass
# Derived class
class DerivedClass(BaseClass):# DerivedClass is derived from BaseClass
    pass
# Create objects b (Of type BaseClass) and d (of type DerivedClass)
b = BaseClass()
d = DerivedClass()
print('Type of b ', type(b))
print('Type of d ', type(d))
print('Parent of d',DerivedClass.__bases__)#.__bases__ -> parent of derived class
print('Parent of b ',BaseClass.__bases__)# All classes inherit from class object
Type of b  <class '__main__.BaseClass'>
Type of d  <class '__main__.DerivedClass'>
Parent of d (<class '__main__.BaseClass'>,)
Parent of b  (<class 'object'>,)

14.3. Single inheritance
14.3.1. A simple inheritance of all functionalities of the base class or parent class.
The simplest case would be when the derived class simply inherits all the functionalities of the base class without adding any functionality of its own. Even though it is the simplest case, it may not be very useful. This will be clear from following example. The following script creates a Pet class and then inherits a Dog class from the Pet class.

In [2]:
class Pet:      # Parent class
    def __init__(self, pName= 'No name'):
        self.pName = pName

class Dog(Pet): # Derived class. Has no __init__() of its own
    pass
p = Pet('my Pet')   #Create an instance of Pet
d = Dog('Tommy')    #Create an instance of Dog
print(p.pName,type(p))  # Output is my Pet <class '__main__.Pet'>
print(d.pName, type(d))  # Output is Tommy <class '__main__.Dog'>
my Pet <class '__main__.Pet'>
Tommy <class '__main__.Dog'>

The following script illustrates the concept:-
The script is given on page 345 of the book

In [3]:
class Animal: # Base class
    def speak(self):
        print('Animal-> Animal sounds')

    def walk(self):
        print('Animal-> I can walk')

class Dog(Animal): # Derived class Dog
    def speak(self):
        print('Dog-> Bark!')

class Cat(Animal): # Derived class Cat
    def speak(self):
        print("Cat-> Meow!")
# Create objects a (Of type Animal), d (Of type Dog) and c (Of type Cat)
a = Animal() # Create object of type Animal
d=Dog()     # Create object of type Dog
c=Cat()     # Create object of type Cat

a.speak()   # Prints Animal-> Animal sounds
d.speak()   # Prints Dog-> Bark!
c.speak()   # Prints Cat-> Meow!
a.walk()    # Prints Animal-> I can walk
d.walk()    # Prints Animal-> I can walk
c.walk()    # Prints Animal-> I can walk
Animal-> Animal sounds
Dog-> Bark!
Cat-> Meow!
Animal-> I can walk
Animal-> I can walk
Animal-> I can walk

14.3.2. Inheritance, where the derived class has an __init__() method of its own
You can derive a class and write an __init__() method of the derived class. But if you do so, you will override the __init__() method of the base class and the __init__() method of the base class will not be called. This is shown as follows:

In [4]:
class Pet:
    def __init__(self, pName= 'No name'):
        self.pName = pName
        print('__init__() of Pet class called')

class Dog(Pet):     # Derived class 
    def __init__(self, pName= 'Dog'):   # __init__() of derived class
        self.pName = pName
        print('__init__() of Dog Class called')
# Create objects p (Of class Pet) and d (Of class Dog)
p = Pet('my Pet')   #Call __init__() of Pet Class
d = Dog('Tommy')    #Call __init__() of Dog Class
print(p.pName)
print(d.pName)  
__init__() of Pet class called
__init__() of Dog Class called
my Pet
Tommy

Take another example to show how easy it is to inherit from a base class and to create classes and objects which can do much more than the base class. In the above example you had a Pet class and a Dog class. Suppose you want to have the Dog class with colour of Dog as a parameter and also give colour to the Dog object during its creation. You can do this as shown in the following code:
The script is given on page 348 of the book

In [5]:
class Pet:
    def __init__(self, pName= 'No name'):
        self.pName = pName
        print('init() of Pet class called')

class Dog(Pet): 
    def __init__(self, pName= 'Dog', color = "Black"):
        self.pName = pName
        self.color = color
        print('init() of Dog Class called')

d = Dog('Tommy')    #Call init() of Dog Class
print(d.pName, d.color)  
d2 = Dog("Muffy", "Brown")
print(d2.pName, d2.color)
init() of Dog Class called
Tommy Black
init() of Dog Class called
Muffy Brown

14.3.3. Both the derived class and the parent class have their own __init__() methods
In Python there may be a situation where you want to use the __init__() methods of both the derived Class and the Parent class, because you may want some of the initialization to be done in the __init__() method of the derived class and rest of the initialization to be done in the __init__() method of the Base Class. If you want to call the __init__() method of the base class then you have to

  1. Make this call from inside the __init__() method of the derived class
  2. Make a call to the __init__() method of base class using the keyword super(). Here again there is a slight difference in Python 2.x and Python 3.x.

The syntax for the two version is shown in the following code:

super(DerivedClass, self).__init__()     # In Python 2.x
super().__init__()                  # In Python 3.x

There are two things to note in the call to __init__() of the base class:

  • The super() does not have a self-parameter in Python 3.x though it does have a self parameter in Python 2.x.
  • If you want to pass any parameter (other than self), you must pass it here.

This will become clear from the following example:
The script is given on page 349 of the book

In [6]:
class Pet:
    def __init__(self, pName= 'No name'):
        self.pName = pName

class Dog(Pet): 
    def __init__(self, pName, sound= 'bark'):
        self.sound = sound
        #super(Dog, self).__init__(pName)     In Python 2.x
        super().__init__(pName)              # In Python 3.x
# Create object d
d = Dog('Tommy', 'Woff!')    #Create an instance of Dog
print('Sound is -> ', d.sound)
print('Name of pet-> ', d.pName)
Sound is ->  Woff!
Name of pet->  Tommy

14.3.4. Use of super() to call methods other than __init__() of base class also
In the above example, the super() method had been used to call the __init__() of the base class. But you can use the super() to call methods other than __init__() of base class also. Suppose you have a method in the base class, you can implement the same method in child class also. If you do so you may override the method of the base class. This is shown in the following code:
The script is given on page 350 of the book

In [7]:
class Pet:
    def __init__(self, pName= 'No name'):
        self.pName = pName
    def walk(self):
        print('Pet is walking')

class Dog(Pet): 
    def __init__(self, pName, sound= 'bark'):
        self.sound = sound
        #super(Dog, self).__init__(pName)     In Python 2.x
        super().__init__(pName)             # In Python 3.x
    def walk(self):
        print('Dog is walking')
        #super(Dog, self).walk()              In Python 2.x
        super().walk()                      # In Python 3.x
# Create object d
d = Dog('Tommy', 'Woff!')    #Create an instance of Dog
d.walk()
Dog is walking
Pet is walking

14.3.5. Calling the __init__() methods of the parent class by using the name of the parent class
It is possible to access the overridden methods of the parent class by using the name of the parent class. This can be done for the __init__() method and also for other methods. Following is the code where the derived class first calls its own __init__() and then specifically calls the __init__() of the parent class by using the name Pet of the parent class:
The script is given on page 351 of the book

In [8]:
class Pet:
    def __init__(self, pName= 'No name'):
        self.pName = pName
        print('constructor of Pet class called')

class Dog(Pet): 
    def __init__(self, pName, sound= 'bark'):
        print('Constructor of Dog class called')
        Pet.__init__(self, pName) #Calling __init__() of Pet class

# Create object d of class Dog
d = Dog('Tommy', 'Woff!')    #Create an instance of Dog
print('Name of pet-> ', d.pName) 
Constructor of Dog class called
constructor of Pet class called
Name of pet->  Tommy

14.3.6. Abstract methods
The script is given on page 352 of the book
In Python, it is possible to create a class with a method but the method is not implemented in the class.
So to use this method, you must derive a child class from this parent class and then implement the method in the child class.
The question then is why would you need to do this?
The answer can be given by an example: suppose you have a Pet class from which you derive various child classes like say Dog and Cat. You want both the Dog and the Cat class to have a method say speak(), but you don’t want to implement the speak() method in the parent Pet class. This can be implemented by having an abstract speak() method in base class which is implemented in child classes Dog and Cat.

In [9]:
class Pet:
    def speak(self):    # Abstract method
        raise NotImplementedError("Please implement this method")

class Dog(Pet):
    def speak(self):    # Abstract method implemented in Dog class
        print('Dog barks')

class Cat(Pet):         # Abstract method not implemented in Cat class
    pass
# Create objects d of Dog class and c of Cat class
d = Dog()
d.speak() # output is Dog barks
c = Cat() 
c.speak() #Error
Dog barks
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-9-d247659cf497> in <module>()
     13 d.speak() # output is Dog barks
     14 c = Cat()
---> 15 c.speak() #Error

<ipython-input-9-d247659cf497> in speak(self)
      1 class Pet:
      2     def speak(self):    # Abstract method
----> 3         raise NotImplementedError("Please implement this method")
      4 
      5 class Dog(Pet):

NotImplementedError: Please implement this method

14.4. Multiple inheritance
Python provides limited support for multiple inheritance also. The syntax for creating a derived class from three different Base classes is shown in the following pseudo code:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

14.4.1. Potential problem in multiple inheritance
In multiple inheritance there is one potential problem. Suppose a method is defined in more than one parent class, then which of the methods should be implemented? In Python, the rule is depth-first, left-to-right. What does this mean?

  • It means in the above example, the Python interpreter will first check up the leftmost parent class which here is Base1 and go all up to its parent, i.e., to the greatest depth.
  • But if it does not find the implementation in Base1 or any of its parents, it will next look up the next parent to the right which is Base2 and go right up to all its parents.
  • If it does not find the implementation of the method in its parents, it will go to Base3 and so on.
  • So the algorithm first goes to the entire depth of the leftmost class, i.e., Base1 (the entire depth means look up all the parent classes of Base1 as well). If the method is not found in the leftmost Parent or Base class, then it searches the entire depth of the base class to its right and so on.

This will become clear from the following example, where you have a GrFather, i.e., GrandFather Class. The GrFather class has two derived classes, i.e., Father and Mother classes. From these you get two child classes Child1 and Child2. The difference between Child1 and Child2 is the order of the parents Father and Mother. This is shown in Figure 14.3 (Not shown here but given in the book).
The script is as follows:
The script is given on page 354 of the book

In [10]:
class GrFather: # GrandFather
    def snore(self):
        print('Grandfather snoring')

class Father(GrFather): # Father derived class from GrFather
    def earn(self):
        print('Father earns')

class Mother(GrFather): # Mother derived class from GrFather
    def earn(self):
        print('Mother earns')    

class Child1(Father, Mother): # parent class Father comes before Mother
    pass

class Child2(Mother, Father): # parent class Mother comes before Father
    pass
# Create objects c1 and c2 of classes Chid1 and Child2
c1 = Child1()
c2 = Child2()
c1.snore() # Call snore() of GrFather class
c1.earn() # Will Call earn() of Father Class
c2.earn() # Will Call earn() of Mother class
Grandfather snoring
Father earns
Mother earns

14.4.4. Creating custom containers
In Python it is possible to create your own custom containers. For example, you could create a class say Vehicle and then have a vehicle object which in turn could act just like a list like say vehicle[0], vehicle[1], etc. To do this, Python provides many methods. Two of them are discussed here. They are __getitem__() and __setitem__(). So if a class implements a __setitem__() method, then the object can have index or keys and those keyed items can be set to values. So if a class say Vehicle implements __setitem__(), then you could have objects of Vehicle class with index or keys. The following example will clarify the concept:
The script is given on page 356 of the book

In [11]:
class Vehicle(object):
     def __init__(self, totalV):
         self.totalV = [None]*totalV
     def __setitem__(self, vehicle_number, vehicle_name):
          self.totalV[vehicle_number] = vehicle_name
     def __getitem__(self, vehicle_number):
          return self.totalV[vehicle_number]


# Create object vehicle of class Vehicle
vehicle = Vehicle(3)
# Since Vehicle class implements __setitem__() and __getitem__()
# you can have index for vehicle object
vehicle[0] = 'truck'
vehicle[1] = 'car'
print('vehicle[0]->', vehicle[0], 'vehicle[1]->', vehicle[1])
# Note vehicle[2] also exists but its value is None
print('vehicle[2]->', vehicle[2])
vehicle[0]-> truck vehicle[1]-> car
vehicle[2]-> None

14.5. Concept of namespace
So you can actually think of the name space as a dictionary (let’s call it dictNamespace) as follows:

dictNamespace = {‘varName1’: object1, ‘varName2’: object2,’varName3’: object3}

14.5.1. locals() and globals()
To understand this example you need to understand two globally defined built-in functions namely locals() and globals() available in Python.
The functions work in the following manner: the return values of these functions are dictionaries of all the variables as keys (the keys are returned as strings) and their values as values of the dictionary.
This is shown in the following code snippet on IDLE. Here you create two variables myInt and myList and also one function myFunc() which doesn’t do anything. Now when you use locals(), you get a dictionary (as indicated by two curly braces { and } marking beginning and end).
In this dictionary, the keys are all surrounded by single quotes, i.e., (‘’), so all the keys are strings. The values are actual values. The keys corresponding to variables created by the user (i.e., user defined) and their values are next to them. Each key is separated from its value by a colon as follows:
The script is given on page 358 of the book

# ---ON IDLE---
>>> myInt = 10
>>> myList = ['a', 'b']
>>>def myFunc(): pass

>>> locals()
{'__name__': '__main__', '__package__': None, '__spec__': None, 'myInt': 10, 'myFunc': <function myFunc at 0x02307228>, '__loader__': <class'_frozen_importlib.BuiltinImporter'>, '__builtins__': <module 'builtins' (built-in)>, 'myList': ['a', 'b'], '__doc__': None}

14.5.2. Namespace dictionary __dict__
As explained above, every namespace is a key value pair. The key is the name of the attribute in the namespace and its value is the value of that attribute in that namespace. This dictionary of attributes and their corresponding values can be accessed using the __dict__ attribute of the class or its instance. Please remember that a class has a different namespace than its instance. Suppose you have a class say myClass and an instance of this class say myObject. Then the namespace of myClass.__dict__ is different from the namespace myObject.__dict__
The following script shows this:
The script is given on page 358 of the book

In [12]:
# class_namespace_instance_namespace.py
class Dog:
    dog_sound = 'bark'# Class variable common to all instances of Dog
    def __init__(self, color):
        self.color = color

# Create instance of Dog
blackDog = Dog('black')

# class and instance namespace
print('Class namespace-> ',Dog.__dict__)    # Gives Class namespace
print('Instance or object namespace-> ', blackDog.__dict__)# object namespace

# You can add attributes and their values to instance namespace
# A new attribute dog_act with value 'Wag tail' added
blackDog.__dict__['dog_act'] = 'Wag tail'
print("New attribute 'dog_act' with value ->",blackDog.__dict__['dog_act'])
# The class attribute dog_sound modified
blackDog.__dict__['dog_sound'] = 'Loud Bark'
print('Class attribute dog_sound changed->',blackDog.__dict__['dog_sound'])
Class namespace->  {'__module__': '__main__', 'dog_sound': 'bark', '__init__': <function Dog.__init__ at 0x04D03108>, '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': None}
Instance or object namespace->  {'color': 'black'}
New attribute 'dog_act' with value -> Wag tail
Class attribute dog_sound changed-> Loud Bark

14.7. Exercise
b. Write a script which does the following:

  • Create a parent class Human
  • In this Human class, write an abstract method nationality()
  • Derive a subclass Indian from class Human
  • In this derived subclass, implement the method nationality() such that it prints ‘Indian’
  • Create an object indian from class Indian

Solution:- The solution is not given in the book

In [13]:
class Human:
    def nationality(self):
        raise NotImplementedError("Please implement this method")
class Indian(Human):
    def nationality(self):
        print("Indian")
        
# Create object        
indian = Indian()
# Call nationality method of subclass
indian.nationality()
Indian

14.8.2. __new__() versus __init__() methods
So far it has been said that the __init__() method is the “constructor” of objects. This is not completely true even though it does serve the purpose for most script writing. Beginning with Python 3.x, there is a method __new__() which is the actual constructor. The following code shows that __new__() is always called before __init__():-
The script is given on page 362 of the book

In [14]:
class X(object):
    def __init__(self):
        print('__init__() called')
    def __new__(cls):
        print('__new__() called')
        return super().__new__(cls)
# Create an object
x = X()
__new__() called
__init__() called

Note: If you override the __new__() method and do not call super() on __new__(), the __init__() method will never be executed. This is shown in the following code where the line of code calling super() is “commented out”:

In [15]:
class X(object):
    def __init__(self):
        print('__init__() called')
    def __new__(cls):
        print('__new__() called')
        # super() commented out
        # return super().__new__(cls)
# Create an object
x = X()
__new__() called

14.8.3. Understanding meta-classes in Python
Following aspects of “meta-classes” in Python are relevant:

  • A meta-class in Python is a “class of class”.
  • Just like a class lays down the “template” of an object, similarly a meta-class lays down the “template” of a class.
  • Just like an object in Python is an “instance” of a class, similarly you can think of a class as an “instance” of a meta-class.
  • So just like a class can be thought as an “object factory”, similarly you can think of a meta-class as a “class-factory”.
  • In Python, the name of meta-class (at the top of the hierarchy of meta-classes) is “type”. Note that the word “type” can be used in two different ways: o First: Use type(some_item) to get the “type of that ” item. (The item could be a function, method, object, class, etc.) o Second: Use the keyword type in creating a class from the type meta-class. So when you use a class definition to create a class, you inherit your class from the class at the top of hierarchy which is object . However, when you create a meta-class you don’t inherit your meta-class from object, rather, you inherit your meta-class from type. So the keyword class can be used to create both classes as well as meta-classes. This will be clear from the followig script:-
    The script is given on page 363 of the book
In [16]:
# 1. AClass() implicitly inherits from object
class AClass():
    pass
# 2. BClass() explicitly inherits from object
class BClass(object):
    pass
# 3. CClass() is subclass of AClass()
class CClass(AClass):
    pass
# 4. AMeta() is a metaclass (Not an ordinary class)
class AMeta(type):
    pass
# 5. BMeta() is a class. It does not inherit from type but from AMeta()
class BMeta(metaclass = AMeta):
    pass
# Check type of class and metaclass
print(type(AClass))  # <class 'type'>
print(type(AMeta))  # <class 'type'>
# Create objects
ac = AClass()
print(type(ac))  # <class '__main__.AClass'>
bm = BMeta()
print(type(bm))  # <class '__main__.BMeta'>
print(isinstance(BMeta, AMeta)) # True
<class 'type'>
<class 'type'>
<class '__main__.AClass'>
<class '__main__.BMeta'>
True

Note that there is another way to create a meta-class, i.e.,, by using the keyword __metaclass__. If you define the attribute __metaclass__ = SomeMetaClass, then Python will use that meta-class to create your class. The use is as follows:
The script is given on page 364 of the book

In [17]:
class XMeta(type):
    pass
class YMeta:
    __metaclass__ = XMeta

a_obj = YMeta()
print(a_obj)  # <__main__.YMeta object at 0x004DD970> 
print(a_obj.__metaclass__)  # <class '__main__.XMeta'>
<__main__.YMeta object at 0x03B51590>
<class '__main__.XMeta'>

In Python 3.x, the preferred way is to give a keyword argument pair (keyword is meta-class and the argument is the name of the meta-class from which the class is derived) in the list of base classes. This is shown in the following code:

In [18]:
class XMeta(type):
    pass
class YMeta(object, metaclass = XMeta):
    pass

14.8.4. “Real subclasses” versus “Virtual subclasses” In Python you can have:

  • Real subclasses (i.e., a class which inherits from another class)
  • Virtual subclass (i.e., a class which does not inherit from a parent class but is “registered” with the parent class).

The concept is very simple and can be explained as follows:

  • In a real subclass, the child class (or derived class or subclass) is inherited from the parent class. The important thing to note is that the child class knows who his parent is but the parent does not know who his child/children is/are.
  • However, in virtual subclass, the child gets “registered” with the parent class. So the parent knows who his child/ children is/are, but the child class does not know who his parent(s) is/ are. Using real and virtual subclasses:-
  • Real subclassing: It is used in normal coding and has already been explained in the text.
  • Virtual subclassing: Python has classes that can register other classes. So once a class is registered with an (Abstract Base Class) ABC, that ABC becomes a “virtual parent” of the subclass. Note that such classes which can become virtual parents of other classes (through registration), are called Abstract Base Classes, or ABCs.

The following code shows how a class can become a child class by register() method of ABC class or a class derived from ABC
The script is given on page 365 of the book

In [19]:
from abc import ABCMeta

class FromABC(metaclass = ABCMeta):
    pass
    
class X(object):
    def __init__(self):
        print('X created')

# Register X as a sub-class of FromABC
FromABC.register(X)
# Create instance of X
x = X()
# Check whether object x is instance of FromABC
print('is x instance of FromABC? ', isinstance(x, FromABC))
print('X.mro()->', X.mro())
X created
is x instance of FromABC?  True
X.mro()-> [<class '__main__.X'>, <class 'object'>]

14.8.5. Abstract Base Class Module, i.e., abc
Here two items are used from the module “abc”. They are:

  • A helper class abc.ABC
  • A decorator, i.e., @abc.abstractmethod. This decorator is used to declare a method as an abstract method.

Declaring a method as abstract method ensures that all inherited classes from this class are forced to implement this function.
The following code shows how to create abstract classes by subclassing (i.e., inheriting) from abc.ABC:-

In [20]:
import abc
# MyABC is inherited from abc.ABC 
class MyABC(abc.ABC):
    
    @abc.abstractmethod
    def some_method(self):
        print("Inside abstract class myABC")
# FromMyABC is inherited from MyABC        
class FromMyABC(MyABC):
    def some_method(self):
        print("Inside derived class FromMyABC")
# Create an object of the derived class FromMyABC        
an_object = FromMyABC()
an_object.some_method()
Inside derived class FromMyABC

Now another point to note is that you may always call an abstract method of our class MyABC from our derived class, i.e., FromMyABC, using the super() keyword as shown in the following code. (A single line of code has been added. As a result of adding this line, the some_method() abstract method of MyABC class is also called):
The script is given on page 366 of the book

In [21]:
import abc
# MyABC is inherited from abc.ABC 
class MyABC(abc.ABC):
    
    @abc.abstractmethod
    def some_method(self):
        print("Inside abstract class myABC")
#FromMyABC is inherited from MyABC        
class FromMyABC(MyABC):
    def some_method(self):
        super().some_method()
        print("Inside derived class FromMyABC")
# Create an object of the derived class FromMyABC        
an_object = FromMyABC()
an_object.some_method()
Inside abstract class myABC
Inside derived class FromMyABC